spb/internetpressure
Public
TypeScript 36.3%
Python 31.8%
Go 18%
JavaScript 9.8%
Shell 1.9%
SQL 1.4%
CSS 0.5%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { notFound } from 'next/navigation';4import { DayStrip, LargestGrid, TopEvents, TopLists } from '@/components/history/HistoryViews';5import { Section, Stat } from '@/components/ui/primitives';6import { apiGet } from '@/lib/api';7import { MONTH_NAMES, fmt, fmtInt } from '@/lib/format';8import type { HistorySummary } from '@/lib/types';910export const dynamic = 'force-dynamic';1112type Params = Promise<{ year: string; month: string }>;1314export async function generateMetadata({ params }: { params: Params }): Promise<Metadata> {15 const { year, month } = await params;16 const name = MONTH_NAMES[Number(month) - 1] ?? month;17 return { title: `History ${name} ${year}`, description: `Daily Global Internet Pressure for ${name} ${year} with the largest events and most affected networks.` };18}1920export default async function HistoryMonthPage({ params }: { params: Params }) {21 const { year, month } = await params;22 const y = Number(year);23 const m = Number(month);24 if (!/^\d{4}$/.test(year) || !(m >= 1 && m <= 12)) notFound();25 const s = await apiGet<HistorySummary>(`/api/v1/history/summary?year=${y}&month=${m}`);26 const days = s.days ?? [];27 const max = days.length ? Math.max(...days.map((d) => d.max)) : null;28 const min = days.length ? Math.min(...days.map((d) => d.min)) : null;29 const avg = days.length ? days.reduce((a, d) => a + d.avg, 0) / days.length : null;30 const events = days.reduce((a, d) => a + d.events, 0);31 const idx = s.available_months.indexOf(`${year}-${String(m).padStart(2, '0')}`);32 const prev = idx > 0 ? s.available_months[idx - 1] : null;33 const next = idx >= 0 && idx < s.available_months.length - 1 ? s.available_months[idx + 1] : null;34 const toHref = (ym: string) => `/history/${ym.split('-')[0]}/${Number(ym.split('-')[1])}`;35 return (36 <div className="pb-8">37 <header className="pt-6 pb-4">38 <p className="label">39 <Link href="/history" className="hover:text-ink">40 History41 </Link>{' '}42 ·{' '}43 <Link href={`/history/${year}`} className="hover:text-ink">44 {year}45 </Link>46 </p>47 <div className="mt-1 flex flex-wrap items-baseline gap-4">48 <h1 className="text-[26px] font-medium tracking-tight sm:text-[32px]">49 {MONTH_NAMES[m - 1]} <span className="num">{year}</span>50 </h1>51 <nav className="num flex gap-2 text-[12px]" aria-label="Adjacent months">52 {prev && (53 <Link href={toHref(prev)} className="text-ink-2 hover:text-ink">54 ← {prev}55 </Link>56 )}57 {next && (58 <Link href={toHref(next)} className="text-ink-2 hover:text-ink">59 {next} →60 </Link>61 )}62 </nav>63 </div>64 <div className="mt-4 grid grid-cols-2 gap-4 sm:grid-cols-5">65 <Stat label="days observed" value={fmtInt(days.length)} />66 <Stat label="max" value={<span style={{ color: max == null ? undefined : `var(--p-${max <= 10 ? 'calm' : max <= 25 ? 'normal' : max <= 40 ? 'elevated' : max <= 55 ? 'stressed' : max <= 70 ? 'high' : max <= 85 ? 'severe' : 'extreme'})` }}>{fmt(max)}</span>} />67 <Stat label="avg" value={fmt(avg)} />68 <Stat label="min" value={fmt(min)} />69 <Stat label="events" value={fmtInt(events)} />70 </div>71 </header>72 <Section label="Calendar" right={<span>daily max pressure</span>}>73 <DayStrip days={days} year={y} month={m} />74 </Section>75 <Section label="Largest events">76 <LargestGrid largest={s.largest} />77 </Section>78 <TopLists s={s} />79 <Section label="Events this month">80 <TopEvents events={s.top_events.filter((e) => e.started_at.startsWith(`${year}-${String(m).padStart(2, '0')}`))} />81 </Section>82 </div>83 );84}85